/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package daemonClient;

import java.awt.TextField;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;

import javax.swing.JDialog;
import javax.swing.JOptionPane;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class DaemonClient
{
	private static String _ip = "127.0.0.1"; // IP adresa server
	private static int _port = 2107; // port serveru
	private static Socket _socket; // promenna pro socket
	protected Shell shell; // shell je hlavni ovladaci prvek okna -> vazou se na nej vsechny elementy (mainpanel v netbeans)
	private BufferedOutputStream bout = null; // vystupni proud dat
	private BufferedInputStream bin = null; // vstupni proud dat
	private Button connectButton, RestartButton, shutdownButton, disconnectButton,
			exitButton = null; // tlacitka
	private Text display = null; // display pro zobrazovani zprav
	private TextField password = new TextField(""); // pole pro zadani hesla
	private JDialog dialog = null; // dialog do ktereho se zadava pole pro heslo
	private Reader reader = null; // promenna pro ovladani tridy reader (slouzi ke cteni zprav od serveru)

	public static void main(String[] args)
	{
		try
		{
			DaemonClient window = new DaemonClient(); // vytvoreni nove instance aplikace
			window.open(); // interni metoda ->
		}
		catch (Exception e)
		{
		}
	}

	public void open()
	{
		Display display = Display.getDefault(); // ziskani defaultniho displeje
		createContents(); // interni metoda ->
		shell.open(); // otevreni okna
		shell.layout(); // vykresleni obsahu
		while (!shell.isDisposed()) // dokud shell neni zrusen
		{
			if (!display.readAndDispatch()) // pokud aplikace nema co zobrazit (fronta je prazdna)
			{
				display.sleep(); // uspi ji
			}
		}
	}

	/**
	 * Create contents of the window.
	 */
	protected void createContents()
	{
		// vytvoreni noveho okna se sazanymi parametry:
		// shell_trim prida standardni vlastnosti okna (vypnou,minimalizovat,maximilizovat a nazev okna)
		// ~SWT.RESIZE & ~SWT.MAX odebere moznost zvetseni a maximilizace
		shell = new Shell(SWT.SHELL_TRIM & ~SWT.RESIZE & ~SWT.MAX);
		shell.setSize(450, 300); // nastaveni velikosti okna
		shell.setText("Daemon Client"); // nastaveni nadpisu okna

		// vytvoreni tlacitek: prvni parametr konstruktoru znaci shell, ke kteremu se ma element pridat
		// druhy parametr znaci styl tlacitka. NONE je standartni tlacitko.
		connectButton = new Button(shell, SWT.NONE);
		connectButton.addSelectionListener(new SelectionAdapter() // pridani obsluhy udalosti
		{ // tento adapter obsluhuje levy klik mysi
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					_socket = new Socket(_ip, _port); // pripojeni na ip a port
					bout = new BufferedOutputStream(_socket.getOutputStream()); // odesilaci stream
					bin = new BufferedInputStream(_socket.getInputStream()); // prijmaci stream
					dialog.toFront(); // dialog pro zadani hesla: prenest do popredi
					dialog.requestFocus(); // vyzadat si pozornost od ostatnich aplikaci
					dialog.setAlwaysOnTop(true); // nastavit dialog jako vzdy na vrchu
					dialog.setVisible(true); // nastavit viditelny
					getPhrase(); // prenest sifrovaci klic
					reader = new Reader(); // nova instance cteci tridy
					reader.start(); // zapnuti vlakna
					writeData(Sifrovani.encrypt("0x05" + password.getText())); // odeslani hesla
					display.append("Connected and password sent.\n"); // pridani zpravy na display
					setVisibilityForAllExceptConnectButton(true); // nastavit viditelnost tlacitek
				}
				catch (Exception ee) // pri zachyceni vyjimky
				{
					try
					{
						if (bout != null)
							bout.close(); // uzavrit odesilaci stream
						if (bin != null)
							bin.close(); // uzavrit prijimaci stream
					}
					catch (IOException e1)
					{
					}
					if (ee.getMessage().contains("Connection refused: connect")) // pokud chybova hlaska obsahuje zadanou frazi
						display.append("Daemon server is not online.\n"); // vypsat na monitor, ze server neni online
					else
						display.append(ee.getMessage() + "\n"); // jinak vypsat celou chybovou hlasku
				}
			}
		});
		connectButton.setBounds(41, 172, 68, 23); // nastaveni polohy sirky a vysky
		connectButton.setText("Connect"); // nastaveni nazvu tlacika

		RestartButton = new Button(shell, SWT.NONE);
		RestartButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					writeData(Sifrovani.encrypt("0x03")); // poslani prikazu pro restart serveru
					disconnect(); // odpojit
					display.append("Restart command sent.\n"); // vypsat hlasku na display
				}
				catch (Exception e1)
				{
					e1.printStackTrace();
				}
			}
		});
		RestartButton.setBounds(115, 172, 68, 23);
		RestartButton.setText("Restart");
		RestartButton.setEnabled(false);

		shutdownButton = new Button(shell, SWT.NONE);
		shutdownButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					writeData(Sifrovani.encrypt("0x04")); // poslani prikazu pro vypnuti
					display.append("Shutdown command sent.\n"); // vypsat hlasku na display
				}
				catch (IOException e1)
				{
					e1.printStackTrace();
				}
			}
		});
		shutdownButton.setBounds(189, 172, 68, 23);
		shutdownButton.setText("Shutdown");
		shutdownButton.setEnabled(false);

		disconnectButton = new Button(shell, SWT.NONE);
		disconnectButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					disconnect(); // odpojit
				}
				catch (Exception e1)
				{
				}
			}
		});
		disconnectButton.setBounds(263, 172, 68, 23);
		disconnectButton.setText("Disconnect");
		disconnectButton.setEnabled(false);

		exitButton = new Button(shell, SWT.NONE);
		exitButton.addSelectionListener(new SelectionAdapter()
		{
			@Override
			public void widgetSelected(SelectionEvent e)
			{
				try
				{
					_socket.close(); // zavrit socket
					bout.close(); // zavrit odchozi stream
					bin.close(); // zavrit prichozi stream
				}
				catch (Exception e1)
				{
				}
				System.exit(1);
			}
		});
		exitButton.setBounds(337, 172, 68, 23);
		exitButton.setText("Exit");
		exitButton.setEnabled(true);

		// vytvoreni textoveho pole pro zobrazeni
		// zapnuti odradkovani, ohraniceni a vertikalni posuvnik
		display = new Text(shell, SWT.WRAP | SWT.BORDER | SWT.VERTICAL);
		display.setBounds(1, 1, 442, 100);
		display.setEditable(false); // nastavit jako needitovatelny

		JOptionPane p = new JOptionPane(""); // nastroj na vyrvareni dialogu
		p.setBounds(shell.getBounds().x + 130, shell.getBounds().y + 50, 200, 200); // nastavit umisteni a velikost
		p.add(password); // pridani TextField password
		dialog = p.createDialog("Enter password"); // vytvoreni dialogu
		dialog.setVisible(false); // zneviditelnit; bude viditelne pozdeji

	}

	// tato metoda nastavi viditelnost tlacitek podle zadane hodnoty
	private void setVisibilityForAllExceptConnectButton(boolean b)
	{
		connectButton.setEnabled(!b);
		RestartButton.setEnabled(b);
		shutdownButton.setEnabled(b);
		disconnectButton.setEnabled(b);
	}

	// synchronizace sifrovaciho klice
	private void getPhrase()
	{
		try
		{
			Thread.sleep(100); // pozastaveni vlakna na 100ms
			// 1st pass
			String data = readData(); // cteni dat z proudu
			Thread.sleep(100); // uspani na 100ms
			Vernam v = new Vernam(); // vytvoreni nove instance vernamovy sifry
			v.setPhrase(data); // nastaveni fraze
			v.generateKey(); // vygenerovani sifrovaciho klice
			writeData(v.XOR()); // xor a odeslani
			Thread.sleep(100); // uspani 100ms
			// 2nd pass
			String s = readData(); // cteni dat
			Thread.sleep(100); // uspani na 100ms
			v.setPhrase(s); // nastaveni fraze
			Sifrovani.setPhrase(new String(v.XOR())); // xor, prevedeni do stringu a nastaveni jako sifrovaci klic
			v.forget(); // vymazani klice a zpravy
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	class Reader extends Thread
	{
		@Override
		public void run()
		{
			while (!isInterrupted()) // dokud neni vlakno preruseno
			{
				try
				{
					final String msg = Sifrovani.decrypt(readData()); // precteni zpravy a odsifrovani

					if (msg == null)
						continue;

					if (msg.contains("0x06"))
					{
						// veskere upravy na prvcich shellu mimo createContents musi byt provadeny pres asyncExec
						// ve tride Display jinak SWT vyhazuje vyjimku
						Display.getDefault().asyncExec(new Runnable()
						{
							@Override
							public void run()
							{
								try
								{
									display.append("Bad password.\n"); // pridat na displej hlasku o spatnem hesle
									_socket.close(); // odpojit socket
									bin.close(); // zavrit prichozi
									bout.close(); // a odchozi stream
									setVisibilityForAllExceptConnectButton(false); // nastavit viditelnost tlacitek
									interrupt(); // prerusit cteni zprav
								}
								catch (Exception ee)
								{
								}
							}
						});
					}
					Thread.sleep(100);
				}
				catch (InterruptedException e)
				{
				}
				catch (Exception e)
				{
					disconnect();
					this.interrupt();
				}
			}
		}
	}

	// Posila data pres sit ke klientovi
	private void writeData(String s) throws IOException
	{
		byte[] msg = s.getBytes(); // rozdeleni zpravy na pole typu byte
		int len = msg.length + 2; // zjisteni velikosti zpravy
		bout.write(len & 0xff); // odeslani velikosti zpravy
		bout.write(len >> 8 & 0xff); // aby klient vedel jak dlouho ma cist a zda mu prisla zprava cela
		bout.write(msg); // odeslani samotne zpravy
		bout.flush(); // uvolneni odesilaci pameti
	}

	public void disconnect()
	{
		try
		{
			_socket.close(); // uzavreni socketu
			bout.close(); // uzavreni odchoziho
			bin.close(); // a prichoziho streamu
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		Display.getDefault().asyncExec(new Runnable()
		{

			@Override
			public void run()
			{
				setVisibilityForAllExceptConnectButton(false); // nastaveni viditelnosti tlacitek
			}
		});
		reader.interrupt(); // zruseni cteni zprav od serveru
	}
	
	// Prijma data pres sit od klienta
		private String readData() throws IOException
		{
			int lengthLo = bin.read(); // cteni spodnich bitu delky
			int lengthHi = bin.read(); // cteni hornich bitu delky
			int length = lengthHi * 256 + lengthLo; // vypocet celkove delky

			byte[] data = new byte[length - 2]; // vytvoreni docasneho pole, kam se bude zprava ukladat

			int receivedBytes = 0; // pocet prenesenych dat
			int newBytes = 0; // cislo prenasenych dat (-1 pokud dosahne konce streamu)
			int left = length - 2; // zbyvajici pocet dat
			while (newBytes != -1 && receivedBytes < length - 2) // dokud cteni nedostahne konce streamu
			{
				// cteni dat. metoda read je rekurzivni, proto se uklada do vstupniho pole, vraci
				// poradove cislo
				newBytes = bin.read(data, receivedBytes, left); // prectenych dat
				receivedBytes = receivedBytes + newBytes; // ulozeni noveho poctu prenesenych dat
				left -= newBytes; // ulozeni noveho poctu zbyvajicich dat
			}
			return new String(data); // prevod zpravy do Stringu a navraceni
		}
}
